# -*- coding: utf-8 -*-
"""
    .xfree.utils.database
    ~~~~~~~~~~~~~~~~~~~~~
    文件描述：数据库操作，包括创建、插入、查询和删除数据-CRUD
    作者：xfree
    邮箱：cy_83k@qq.com
"""
import logging

import pytz
from flask_login import current_user
from flask_sqlalchemy.query import Query
from sqlalchemy.orm import declarative_mixin, declared_attr

from xfree.extensions import db

from ..core.exceptions import PersistenceError

logger = logging.getLogger(__name__)


def make_comparable(cls):
    """
    定义一个装饰器，用于使传入的类具有相等比较和哈希计算功能，最后，将这三个方法添加到传入的类中，并返回修改后的类。
    :cls: 要装饰的类
    :return: 修改后的类
    """
    # 定义相等比较方法
    def __eq__(self, other):
        # 如果 other 是 cls 的实例且两者的 id 相同，则返回 True
        return isinstance(other, cls) and self.id == other.id

    # 定义不相等比较方法
    def __ne__(self, other):
        # 调用相等比较方法的否定形式
        return not self.__eq__(other)

    # 定义哈希计算方法
    def __hash__(self):
        # 返回一个包含类和对象 id 的元组的哈希值
        return hash((cls, self.id))

    # 将相等、不相等和哈希计算方法添加到传入的类中
    cls.__eq__ = __eq__
    cls.__ne__ = __ne__
    cls.__hash__ = __hash__
    # 返回修改后的类
    return cls


class CRUDMixin(object):
    """
    定义一个混合类，用于实现常见的数据库操作（创建、读取、更新、删除 - CRUD）
    :object: 父类
    """
    def __repr__(self):
        """
        定义对象的字符串表示方法，返回类名
        :return: 类名
        """
        return "<{}>".format(self.__class__.__name__)

    @classmethod
    def create(cls, **kwargs):
        """
        创建对象并保存到数据库
        :kwargs: 关键字参数
        :return: 创建的对象
        """
        # 使用传入的关键字参数创建对象实例
        instance = cls(**kwargs)
        # 调用 save 方法保存对象并返回实例
        return instance.save()

    def save(self):
        """
        用于保存对象到数据库
        :return: 保存后的对象
        """
        # 将对象添加到数据库会话
        db.session.add(self)
        # 提交数据库会话
        db.session.commit()
        # 返回保存后的对象
        return self

    def delete(self):
        """
        用于从数据库中删除对象
        :return: 被删除的对象
        """
        # 从数据库会话中删除对象
        db.session.delete(self)
        # 提交数据库会话
        db.session.commit()
        # 返回被删除的对象
        return self


class UTCDateTime(db.TypeDecorator):
    """
    # 定义一个自定义的日期时间类型装饰器
    :db.TypeDecorator: 父类
    :return: UTC 时间
    """
    # 指定底层实现为 SQLAlchemy 的 DateTime 类型
    impl = db.DateTime
    # 表示该装饰器的结果可以被缓存
    cache_ok = True

    def process_bind_param(self, value, dialect):
        """
        在将日期时间值绑定到数据库之前调用
        :dialect: 数据库方言
        :value: 日期时间值
        :return: 转换后的日期时间值
        """
        # 如果值不为 None
        if value is not None:
            # 如果数据库方言是 sqlite 或 mysql
            if dialect.name in ("sqlite", "mysql"):
                # 返回无时区的本地时间
                return value.replace(tzinfo=None)
            # 对于其他支持时区的数据库，返回 UTC 时间
            return value.astimezone(pytz.UTC)

    def process_result_value(self, value, dialect):
        """
        在从数据库中获取日期时间值之后调用
        :value: 日期时间值
        :dialect: 数据库方言
        :return: 转换后的日期时间值
        """
        # 如果数据库方言是 sqlite 或 mysql 且值不为 None
        if dialect.name in ("sqlite", "mysql") and value is not None:
            # 将无时区的本地时间转换为有时区的 UTC 时间
            return value.replace(tzinfo=pytz.UTC)
        # 其他方言已经是无时区的，直接返回值
        return value


class HideableQuery(Query):
    """
    定义一个可隐藏对象的查询类，继承自 Query 类
    :Query: 父类
    """
    # 类属性，表示查询是否包含隐藏对象，默认为 False
    _with_hidden = False

    def __new__(cls, *args, **kwargs):
        """
        重写 __new__ 方法，创建新的查询对象
        :cls: 查询类
        *args: 位置参数
        **kwargs: 关键字参数
        :return: 查询对象
        """
        # 创建查询对象
        obj = super(HideableQuery, cls).__new__(cls)
        # 从 kwargs 中获取是否包含隐藏对象的标志，默认为 False
        include_hidden = kwargs.pop("_with_hidden", False)
        # 如果当前用户存在且有权限查看隐藏内容
        has_view_hidden = current_user and current_user.permissions.get("viewhidden", False)
        # 设置查询对象的 _with_hidden 属性
        obj._with_hidden = include_hidden or has_view_hidden
        # 如果有参数传入，则调用父类的 __init__ 方法并根据是否包含隐藏对象进行过滤
        if args or kwargs:
            super(HideableQuery, obj).__init__(*args, **kwargs)
            return obj.filter_by(hidden=False) if not obj._with_hidden else obj
        # 返回创建的查询对象
        return obj

    def __init__(self, *args, **kwargs):
        """
         空实现的 __init__ 方法
         :args: 位置参数
         :kwargs: 关键字参数
        """
        pass

    def with_hidden(self):
        """
         返回一个新的查询对象，该对象包含隐藏对象
         :return: 包含隐藏对象的查询对象
        """
        return self.__class__(
            self._only_full_mapper_zero("get"),
            session=db.session(),
            _with_hidden=True,
        )

    def _get(self, *args, **kwargs):
        """
         获取单个对象，如果对象不存在、查询包含隐藏对象或者对象未被隐藏，则返回对象；否则返回 None
         :return: 对象或 None
        """
        return super(HideableQuery, self).get(*args, **kwargs)

    # 方法，用于获取单个对象，如果对象不存在、查询包含隐藏对象或者对象未被隐藏，则返回对象；否则返回 None
    def get(self, *args, **kwargs):
        obj = self.with_hidden()._get(*args, **kwargs)
        return obj if obj is None or self._with_hidden or not obj.hidden else None


@declarative_mixin
class HideableMixin(object):
    """
    定义一个可隐藏对象的混合类，继承自 declarative_mixin 类
    :declarative_mixin: 父类
    """
    # 指定该混合类使用的查询类为 HideableQuery
    query_class = HideableQuery

    # 定义一个布尔类型的列，表示对象是否被隐藏，默认为 False
    hidden = db.Column(db.Boolean, default=False, nullable=True)
    # 定义一个使用 UTCDateTime 装饰器的列，表示对象被隐藏的时间
    hidden_at = db.Column(UTCDateTime(timezone=True), nullable=True)

    @declared_attr
    def hidden_by_id(cls):  # noqa: B902
        """
        定义一个整数类型的列，表示隐藏该对象的用户，外键关联到 User 模型，默认为 None
        :return: 整数类型的列
        """
        return db.Column(
            db.Integer,
            db.ForeignKey("users.id", name="fk_{}_hidden_by".format(cls.__name__)),
            nullable=True,
        )

    @declared_attr
    def hidden_by(cls):  # noqa: B902
        """
        定义一个装饰属性，表示隐藏该对象的用户，外键关联到 User 模型
        :return: User 对象
        """
        return db.relationship("User", uselist=False, foreign_keys=[cls.hidden_by_id])

    def hide(self, user, *args, **kwargs):
        """
        用于将对象标记为隐藏，设置隐藏时间和隐藏用户
        :param user: 隐藏该对象的用户
        :return: self
        :args: 位置参数
        :kwargs: 关键字参数
        :return: self
        """
        from xfree.utils.helpers import time_utcnow

        self.hidden_by = user
        self.hidden = True
        self.hidden_at = time_utcnow()
        return self

    def unhide(self, *args, **kwargs):
        """
         将对象标记为未隐藏，清除隐藏时间和隐藏用户
         :return: self
         :args: 位置参数
         :kwargs: 关键字参数
         :return: self
        """
        self.hidden_by = None
        self.hidden = False
        self.hidden_at = None
        return self


class HideableCRUDMixin(HideableMixin, CRUDMixin):
    """
    # 定义一个结合了可隐藏对象和 CRUD 操作的混合类
    """
    pass


def try_commit(session, message="Error while saving"):
    """
    尝试提交数据库会话，如果出现异常，抛出 PersistenceError 异常，并带有指定的错误消息
    :param session: 数据库会话
    :param message: 错误消息
    :return: None
    """
    try:
        # 提交数据库会话
        session.commit()
    except Exception:
        # 如果出现异常，抛出 PersistenceError 异常，并带有指定的错误消息
        raise PersistenceError(message)
